#include "..\include\combinations.h"
#include "..\include\CardSet.h"
#include "..\include\combination.h"

#include <iostream>

using namespace std;

/*****************************************************************************/
/**                             class CCombination                          **/
/*****************************************************************************/

bool CCombination::isPair()
{
    return mEqValues.size() == 4;
}

bool CCombination::isTwoPairs()
{
    int cntPair = 0;
    if ( mEqValues.size() == 3 )
        for ( const pair< CCard::ValueType, int > &p: mEqValues )
            if ( p.second == 2 ) cntPair++;

    return cntPair == 2;
}

bool CCombination::isTheeOfAKind()
{
    if ( mEqValues.size() == 3 )
        for ( const pair< CCard::ValueType, int >& p: mEqValues )
            if ( p.second == 3 ) return true;

    return false;
}

bool CCombination::isFourOfAKind()
{
    if ( mEqValues.size() == 2 )
        for ( const pair< CCard::ValueType, int >& p: mEqValues )
            if ( p.second == 4 ) return true;

    return false;
}

bool CCombination::isFullHouse()
{
    if ( mEqValues.size() == 2 )
        for ( const pair< CCard::ValueType, int >& p: mEqValues )
            if ( p.second == 3 ) return true;

    return false;
}

bool CCombination::isSameSuite()
{
    return ( mEqSuits.size() == 1 ) ;
}

Range CCombination::getRange(const CHand& hand)
{
    Range range;
    range.isValid = false;

    if ( mEqValues.size() == 5 )
    {
        CCard::ValueType minValue(CCard::vKing), maxValue(CCard::vTwo);
        bool ace = false;

        for ( const CCard& card: hand )
        {
            if ( card.getValue() == CCard::vAce )
            {
                ace = true;
                continue;
            }

            if (card.getValue() > maxValue ) maxValue = card.getValue();
            if (card.getValue() < minValue ) minValue = card.getValue();
        }

        if (!ace)
        {
            if ((maxValue - minValue) == 4 )
            {
                range.endOfRange = maxValue;
                range.isValid = true;
                return range;
            }
        }
        else
        {
            if ( (maxValue == CCard::vFive) || (minValue == CCard::vTen) )
            {
                range.endOfRange = (maxValue == CCard::vFive)?CCard::vFive:CCard::vAce;
                range.isValid = true;
                return range;
            }
        }
    }
    return range;
}

bool CCombination::isStraight()
{
    return ( mRange.isValid && ( !isSameSuite()) );
}

bool CCombination::isFlush()
{
    return ( !mRange.isValid && isSameSuite() );
}

bool CCombination::isStraightFlush()
{
    return ( mRange.isValid && (mRange.endOfRange < CCard::vAce) && isSameSuite() );
}

bool CCombination::isRoyalFlush()
{
    return  ( isSameSuite() && mRange.isValid && (mRange.endOfRange == CCard::vAce) );
}

void CCombination::getEqRanges(const CHand& hand)
{
    mEqValues.clear();
    mEqSuits.clear();

    for ( const CCard& card: hand )
    {
        mEqValues[card.getValue()]++;
        mEqSuits[card.getSuit()]++;
    }

    mRange = getRange(hand);
}

COMBINATION_TYPE CCombination::calcCombinationType(const CHand& hand)
{
    getEqRanges(hand);

    if ( isPair() )          return ccPair;
    if ( isTwoPairs() )      return ccTwoPairs;
    if ( isTheeOfAKind() )   return ccThreeOfAKind;
    if ( isFourOfAKind() )   return ccFourOfAKind;
    if ( isFullHouse() )     return ccFullHouse;

    if ( isRoyalFlush() )    return ccRoyalFlush;
    if ( isStraightFlush() ) return ccStraightFlush;
    if ( isFlush() )         return ccFlush;
    if ( isStraight() )      return ccStraight;

    return ccHighCard;
}

/*****************************************************************************/
/**                      class CBestCombinationFinder                       **/
/*****************************************************************************/

CHand CBestCombinationFinder::findBestHand(CCardSet& cardSet)
{
    CHand currHand(cardSet.begin());

    mpBestHand = new CHand(cardSet.begin());
    stdcomb::recursive_combination(cardSet.begin(), cardSet.end(), 0, currHand.begin(), currHand.end(), 0, cardSet.size()-5, *this);

    CHand bestHand(mpBestHand->begin());

    delete mpBestHand;
    return bestHand;
}

void CBestCombinationFinder::operator () (vector<CCard>::iterator begin, vector<CCard>::iterator)
{
    CHand currHand(begin);
    if ( currHand > (*mpBestHand) )
        (*mpBestHand) = currHand;
}

/*****************************************************************************/
/**                      class CBestCombinationFinder7                      **/
/*****************************************************************************/


/*****************************************************************************/
/**                     class CHoldemCombinationsFinder                     **/
/*****************************************************************************/

void CHoldemCombinationsFinder::findAllHands(const CCardSet& hand, const CCardSet& table)
{
    int cardsDealt = hand.size() + table.size();
    int cardsUnknown = 9 - cardsDealt;

    mpDeck = new CDeck();
    mpMyCardSet = new CCardSet(cardsDealt);
    mpOppCardSet = new CCardSet(table.size());

    for (const CCard& card: hand)
    {
        mpMyCardSet->addCard(card);
        mpDeck->removeCard(card);
    }

    for (const CCard& card: table)
    {
        mpMyCardSet->addCard(card);
        mpOppCardSet->addCard(card);
        mpDeck->removeCard(card);
    }

    for (size_t i = 0; i < 7462; i++)
    {
        mMyHandsCount[i] = 0;
        mMyHands[i] = NULL;
        mOppHandsCount[i] = 0;
        mOppHands[i] = NULL;
    }

    CCardSet currCards(cardsUnknown);
    for (int i = 0; i < cardsUnknown; i++ )
        currCards.addCard( CCard("2S") );

    CFunctor func(*this);

    mWin = 0;
    mLose = 0;
    mDraw = 0;

    stdcomb::recursive_combination(mpDeck->begin(), mpDeck->end(), 0, currCards.begin(), currCards.end(), 0, mpDeck->size()-cardsUnknown, func);
}

void CHoldemCombinationsFinder::operator () (vector<CCard>::iterator begin, vector<CCard>::iterator end)
{
    CCardSet mySet(7);
    CCardSet oppSet(7);

    int i = 0;
    for (vector<CCard>::iterator itCard = begin; itCard < end; itCard++ )
    {
        oppSet.addCard(*itCard);
        i++;
        if (i > 2) mySet.addCard(*itCard);
    }

    for (const CCard& card: *mpMyCardSet)
        mySet.addCard(card);

    for (const CCard& card: *mpOppCardSet)
        oppSet.addCard(card);

    CHand myHand = mBestFinder.findBestHand(mySet);
    CHand oppHand = mBestFinder.findBestHand(oppSet);

    int myStrength = myHand.getStrength();
    int oppStrength = oppHand.getStrength();

    if ( myStrength == oppStrength )
        mDraw++;
    else if ( myStrength > oppStrength )
        mWin++;
    else
        mLose++;

    mMyHandsCount[myStrength]++;
    mOppHandsCount[oppStrength]++;

    if ( !mMyHands[myStrength] )
        mMyHands[myStrength] = new CHand(myHand);

    if ( !mOppHands[oppStrength] )
        mOppHands[oppStrength] = new CHand(myHand);
}

/*****************************************************************************/
/**                                   Misc                                  **/
/*****************************************************************************/

string CombinationTypeToString(COMBINATION_TYPE comb)
{
    if ( comb == ccPair ) return "ccPair";
    if ( comb == ccTwoPairs ) return "ccTwoPairs";
    if ( comb == ccThreeOfAKind ) return "ccThreeOfAKind";
    if ( comb == ccStraight ) return "ccStraight";
    if ( comb == ccFlush ) return "ccFlush";
    if ( comb == ccFullHouse ) return "ccFullHouse";
    if ( comb == ccFourOfAKind ) return "ccFourOfAKind";
    if ( comb == ccStraightFlush ) return "ccStraightFlush";
    if ( comb == ccRoyalFlush ) return "ccRoyalFlush";
    if ( comb == ccHighCard ) return "ccHighCard";

    return "";
}

unsigned int getValueStrength(const CCardSet& cardSet)
{
    unsigned int iStrength = 0;
    for (const CCard& card: cardSet)
    {
        unsigned int p = (card.getValue() - CCard::vTwo);
        iStrength += pow<5>(p);
    }
    return iStrength;
}

unsigned int getStraightStrength(const CCardSet& cardSet)
{
    CCard::ValueType maxVal(CCard::vTwo), minVal(CCard::vAce);

    for (const CCard& card: cardSet)
    {
        if (maxVal < card.getValue()) maxVal = card.getValue();
        if (minVal > card.getValue()) minVal = card.getValue();
    }

    if ( (maxVal == CCard::vAce) && (minVal == CCard::vTwo) ) return 1;

    return maxVal;
}

unsigned int getFullHouseStrength(const CCardSet& cardSet)
{
    unsigned int iStrength = 0;

    map<CCard::ValueType, int> mapCouner;
    for (const CCard& card: cardSet)
        mapCouner[card.getValue()]++;

    for (auto it: mapCouner)
        iStrength += pow<13>( it.second - 2 ) * (it.first - CCard::vTwo);

    return iStrength;
}

unsigned int getFourOfAKindStrength(const CCardSet& cardSet)
{
    unsigned int iStrength = 0;

    map<CCard::ValueType, int> mapCouner;
    for (const CCard& card: cardSet)
        mapCouner[card.getValue()]++;

    for (auto it: mapCouner)
    {
        unsigned int power = it.second == 1?0:1;
        iStrength += pow<13>( power ) * (it.first - CCard::vTwo);
    }

    return iStrength;
}

unsigned int getPairStrength(const CCardSet& cardSet)
{
    unsigned int iStrength = 0;

    map<CCard::ValueType, int> mapCouner;
    for (const CCard& card: cardSet)
        mapCouner[card.getValue()]++;

    for (auto it: mapCouner)
    {
        if (it.second == 1)
            iStrength += 1 << (it.first - CCard::vTwo);
        if (it.second == 2)
            iStrength += (it.first - CCard::vTwo) * (1 << 16);
    }

    return iStrength;
}

unsigned int getTwoPairStrength(const CCardSet& cardSet)
{
    unsigned int iStrength = 0;

    map<CCard::ValueType, int> mapCouner;
    for (const CCard& card: cardSet)
        mapCouner[card.getValue()]++;

    for (auto it: mapCouner)
    {
        if (it.second == 1)
            iStrength += it.first - CCard::vTwo;
        if (it.second == 2)
            iStrength += 1 << (it.first - CCard::vTwo + 4);
    }

    return iStrength;
}

unsigned int getThreeOfAKindStrength(const CCardSet& cardSet)
{
    unsigned int iStrength = 0;

    map<CCard::ValueType, int> mapCouner;
    for (const CCard& card: cardSet)
        mapCouner[card.getValue()]++;

    for (auto it: mapCouner)
    {
        if (it.second == 1)
            iStrength += 1 << (it.first - CCard::vTwo);
        if (it.second == 3)
            iStrength += (it.first - CCard::vTwo) << 16;
    }

    return iStrength;
}

unsigned int getCombinationStrength(COMBINATION_TYPE combination, const CHand& hand)
{
    switch (combination)
    {
        case ccPair:          return getPairStrength(hand);
        case ccTwoPairs:      return getTwoPairStrength(hand);
        case ccThreeOfAKind:  return getThreeOfAKindStrength(hand);
        case ccFourOfAKind:   return getFourOfAKindStrength(hand);
        case ccFullHouse:     return getFullHouseStrength(hand);
        case ccStraight:
        case ccStraightFlush: return getStraightStrength(hand);
        case ccHighCard:
        case ccFlush:         return getValueStrength(hand);
        case ccRoyalFlush:    return 1;
        default: throw "";
    }
    return 0;
}
